/*
 * Copyright 2011 android-jar-fixer authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.perunlabs.tool.fixandroidjar;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
 * MethodVisitor that rewrites the method to return the default value of its
 * return type (zero for numbers, false for booleans, null for objects, etc).
 * Cannot be used for constructors.
 */
public class FixMethodMethodVisitor implements MethodVisitor {
  private final String methodName;
  private final String descriptor;
  private final MethodVisitor methodVisitor;
  private boolean codeVisited;

  public FixMethodMethodVisitor(String methodName, String desc, MethodVisitor methodVisitor) {
    this.methodName = methodName;
    this.descriptor = desc;
    this.methodVisitor = methodVisitor;
    this.codeVisited = false;
  }

  @Override
  public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    return methodVisitor.visitAnnotation(desc, visible);
  }

  @Override
  public AnnotationVisitor visitAnnotationDefault() {
    return methodVisitor.visitAnnotationDefault();
  }

  @Override
  public void visitAttribute(Attribute attr) {
    methodVisitor.visitAttribute(attr);
  }

  @Override
  public void visitCode() {
    codeVisited = true;
  }

  @Override
  public void visitEnd() {
    if (codeVisited) {
      methodVisitor.visitCode();
      generateReturnStatementForType(descriptor);
      methodVisitor.visitMaxs(100, 100);
    }
    methodVisitor.visitEnd();
  }

  private void generateReturnStatementForType(String desc) {
    Type type = Type.getReturnType(desc);
    if (type.equals(Type.VOID_TYPE)) {
      methodVisitor.visitInsn(Opcodes.RETURN);
    } else if (type.equals(Type.BYTE_TYPE) || type.equals(Type.SHORT_TYPE)
        || type.equals(Type.CHAR_TYPE) || type.equals(Type.INT_TYPE)
        || type.equals(Type.BOOLEAN_TYPE)) {
      methodVisitor.visitInsn(Opcodes.ICONST_0);
      methodVisitor.visitInsn(Opcodes.IRETURN);
    } else if (type.equals(Type.LONG_TYPE)) {
      methodVisitor.visitInsn(Opcodes.LCONST_0);
      methodVisitor.visitInsn(Opcodes.LRETURN);
    } else if (type.equals(Type.FLOAT_TYPE)) {
      methodVisitor.visitInsn(Opcodes.FCONST_0);
      methodVisitor.visitInsn(Opcodes.FRETURN);
    } else if (type.equals(Type.DOUBLE_TYPE)) {
      methodVisitor.visitInsn(Opcodes.DCONST_0);
      methodVisitor.visitInsn(Opcodes.DRETURN);
    } else {
      if (methodName.equals("getSystemService")) {
        generateCodeThatCreatesAndReturnsDefaultProxy(methodVisitor,
            "android/hardware/SensorManager");
      }

      String returnType = Type.getReturnType(desc).getInternalName();
      if (isAndroidType(returnType)) {
        generateCodeThatCreatesAndReturnsDefaultProxy(methodVisitor, returnType);
      } else {
        generateCodeThatReturnsNull(methodVisitor);
      }
    }
  }

  private static void generateCodeThatCreatesAndReturnsDefaultProxy(MethodVisitor methodVisitor,
      String returnType) {
    methodVisitor.visitLdcInsn(Type.getType("L" + returnType + ";"));
    methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "com/perunlabs/tool/fakeapi/Proxies",
        "createDefaultProxy", "(Ljava/lang/Class;)Ljava/lang/Object;");
    methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, returnType);
    methodVisitor.visitInsn(Opcodes.ARETURN);
  }

  private static void generateCodeThatReturnsNull(MethodVisitor methodVisitor) {
    methodVisitor.visitInsn(Opcodes.ACONST_NULL);
    methodVisitor.visitInsn(Opcodes.ARETURN);
  }

  private static boolean isAndroidType(String returnType) {
    return returnType.startsWith("com/android/") || returnType.startsWith("android/");
  }

  @Override
  public void visitFieldInsn(int opcode, String owner, String name, String desc) {}

  @Override
  public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {}

  @Override
  public void visitIincInsn(int var, int increment) {}

  @Override
  public void visitInsn(int opcode) {}

  @Override
  public void visitIntInsn(int opcode, int operand) {}

  @Override
  public void visitJumpInsn(int opcode, Label label) {}

  @Override
  public void visitLabel(Label label) {}

  @Override
  public void visitLdcInsn(Object cst) {}

  @Override
  public void visitLineNumber(int line, Label start) {}

  @Override
  public void visitLocalVariable(String name, String desc, String signature, Label start,
      Label end, int index) {}

  @Override
  public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {}

  @Override
  public void visitMaxs(int maxStack, int maxLocals) {}

  @Override
  public void visitMethodInsn(int opcode, String owner, String name, String desc) {}

  @Override
  public void visitMultiANewArrayInsn(String desc, int dims) {}

  @Override
  public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
    return null;
  }

  @Override
  public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {}

  @Override
  public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {}

  @Override
  public void visitTypeInsn(int opcode, String type) {}

  @Override
  public void visitVarInsn(int opcode, int var) {}
}
